package api

import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	sharedToken "mxshop-api/shared/auth/token"
	"mxshop-api/user-web/dao/redis"
	"mxshop-api/user-web/forms"
	"mxshop-api/user-web/global"
	"mxshop-api/user-web/global/response"
	proto "mxshop-api/user-web/proto/gen/v1"
	"mxshop-api/user-web/token"
)

// HandleGrpcErrorToHttp 将grpc的code转换成http的状态码
func HandleGrpcErrorToHttp(c *gin.Context, err error) {
	if err != nil {
		if s, ok := status.FromError(err); ok {
			switch s.Code() {
			case codes.NotFound:
				c.JSON(http.StatusNotFound, gin.H{
					"msg": s.Message(),
				})
			case codes.Internal:
				c.JSON(http.StatusInternalServerError, gin.H{
					"msg:": "内部错误",
				})
			case codes.InvalidArgument:
				c.JSON(http.StatusBadRequest, gin.H{
					"msg": "参数错误",
				})
			case codes.Unavailable:
				c.JSON(http.StatusInternalServerError, gin.H{
					"msg": "用户服务不可用",
				})
			default:
				c.JSON(http.StatusInternalServerError, gin.H{
					"msg": s.Code(),
				})
			}
			return
		}
	}
}

// removeTopStruct 去除字段名中的结构体名称
func removeTopStruct(fields map[string]string) map[string]string {
	res := map[string]string{}
	for field, err := range fields {
		res[field[strings.Index(field, ".")+1:]] = err
	}
	return res
}

// HandleValidatorError 统一处理校验错误
func HandleValidatorError(c *gin.Context, err error) {
	// 获取validator.ValidationErrors类型的errors
	errs, ok := err.(validator.ValidationErrors)
	if !ok {
		// 非validator.ValidationErrors类型错误直接返回
		c.JSON(http.StatusInternalServerError, gin.H{
			"msg": err.Error(),
		})
		return
	}
	// validator.ValidationErrors类型错误则进行翻译
	// 并使用removeTopStruct函数标识去除字段名中的结构体名称
	c.JSON(http.StatusBadRequest, gin.H{
		"error": removeTopStruct(errs.Translate(global.Trans)),
	})
	return
}

// GetUserList 获取用户列表
func GetUserList(c *gin.Context) {
	// 绑定参数
	var page forms.Page
	if err := c.ShouldBindQuery(&page); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"msg": "参数有误",
		})
		return
	}

	// 获取token
	claims, ok := c.Get("claims")
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"msg": "get claims failed"})
		return
	}
	currentUser := claims.(sharedToken.Custom)
	zap.L().Info("当前用户", zap.Uint("ID", currentUser.ID))

	UserClient := proto.NewUserClient(global.Conn)
	resp, err := UserClient.GetUserList(c, &proto.PageInfo{
		Pn:    page.Num,
		PSize: page.Size,
	})
	if err != nil {
		zap.L().Error("[GetUserList] 查询 [用户列表] 失败")
		HandleGrpcErrorToHttp(c, err)
		return
	}

	result := make([]interface{}, 0, len(resp.Data)) // 一次性把内存申请到位,可以减少扩容次数,提高速度
	for _, value := range resp.Data {
		user := response.UserResponse{
			Id:       value.Id,
			NickName: value.NickName,
			// Birthday: time.Unix(int64(value.BirthDay), 0),
			Birthday: response.JsonTime(time.Unix(int64(value.BirthDay), 0)), // 转换为自定义的类型
			Gender:   value.Gender,
			Mobile:   value.Mobile,
		}
		result = append(result, user)
	}

	h := gin.H{
		"total": resp.Total,
	}
	h["data"] = result
	c.JSON(http.StatusOK, h)
}

// PasswordLogin 密码登录
func PasswordLogin(c *gin.Context) {
	// 1. 参数校验
	var passwordLogin forms.PasswordLogin
	if err := c.ShouldBind(&passwordLogin); err != nil {
		HandleValidatorError(c, err)
		return
	}

	// 2. 校验用户名、密码的合法性
	UserClient := proto.NewUserClient(global.Conn)
	userInfo, err := UserClient.GetUserByMobile(context.Background(), &proto.MobileRequest{
		Mobile: passwordLogin.Mobile,
	})
	if err != nil {
		// 2.3 判断用户是否存在
		HandleGrpcErrorToHttp(c, err)
		// if s, ok := status.FromError(err); ok {
		//	switch s.Code() {
		//	case codes.NotFound:
		//		c.JSON(http.StatusNotFound, gin.H{"msg": "用户不存在"})
		//	case codes.Unavailable:
		//		c.JSON(http.StatusInternalServerError, gin.H{"msg": "服务器异常,请稍后再试"})
		//	default:
		//		c.JSON(http.StatusBadRequest, gin.H{"msg": "登录失败"})
		//	}
		// }
		return
	}

	// 2.4 判断用户名密码是否正确
	checkPWD, err := UserClient.CheckPassWord(context.Background(), &proto.PasswordCheckInfo{
		Password:          passwordLogin.Password,
		EncryptedPassword: userInfo.PassWord,
	})
	if err != nil || !checkPWD.Success {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "登录失败"})
		return
	}

	// 生成token
	t := token.NewJWTTokenGen(global.ServerConfig.JWTConfig.SigningKey,
		global.ServerConfig.JWTConfig.Issuer, global.ServerConfig.JWTConfig.Expire)
	tokenInfo, err := t.GenerateToken(sharedToken.Custom{
		ID:       uint(userInfo.Id),
		NickName: userInfo.NickName,
		RoleID:   uint(userInfo.Role),
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"msg": "登录成功", "access_token": tokenInfo})
}

func Register(c *gin.Context) {
	// 1.参数校验
	var register forms.Register
	if err := c.ShouldBind(&register); err != nil {
		HandleValidatorError(c, err)
		return
	}

	// 2.验证码校验
	key := fmt.Sprintf(global.RegisterKeyPrefix, register.Mobile)
	ok, err := redis.CheckDelSMSCode(context.Background(), key, register.Code)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"msg": "服务器异常,请稍后再试"})
		return
	}
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "验证码有误"})
		return
	}

	UserClient := proto.NewUserClient(global.Conn)
	_, err = UserClient.GetUserByMobile(context.Background(), &proto.MobileRequest{
		Mobile: register.Mobile,
	})
	if err == nil {
		c.JSON(http.StatusBadRequest, gin.H{"msg": "已注册"})
		return
	}

	// 创建用户
	userInfo, err := UserClient.CreateUser(context.Background(), &proto.CreateUserInfo{
		NickName: register.Mobile,
		Mobile:   register.Mobile,
		PassWord: register.Password,
	})
	if err != nil {
		zap.L().Error("register create user failed", zap.Error(err))
		HandleGrpcErrorToHttp(c, err)
		return
	}

	// 生成token
	t := token.NewJWTTokenGen(global.ServerConfig.JWTConfig.SigningKey,
		global.ServerConfig.JWTConfig.Issuer, global.ServerConfig.JWTConfig.Expire)
	tokenInfo, err := t.GenerateToken(sharedToken.Custom{
		ID:       uint(userInfo.Id),
		NickName: userInfo.NickName,
		RoleID:   uint(userInfo.Role),
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"id":           userInfo.Id,
		"nick_name":    userInfo.NickName,
		"access_token": tokenInfo,
	})
}
